namespace Maple.Branch.Identity
{
    using System;
    using System.Collections.Generic;
    using AuditLogs;
    using Base.Consts;
    using Configurations;
    using Consts;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.OpenApi.Models;
    using Permissions;
    using Settings;
    using StackExchange.Redis;
    using Swashbuckle.AspNetCore.Filters;
    using Swashbuckle.AspNetCore.SwaggerUI;
    using Tenants;
    using Volo.Abp;
    using Volo.Abp.AspNetCore.MultiTenancy;
    using Volo.Abp.Auditing;
    using Volo.Abp.Autofac;
    using Volo.Abp.EntityFrameworkCore;
    using Volo.Abp.EventBus.RabbitMq;
    using Volo.Abp.Modularity;

    [DependsOn(typeof(AbpAspNetCoreMultiTenancyModule),
        typeof(AbpAutofacModule),
        typeof(AbpEventBusRabbitMqModule),
        typeof(IdentityAppServiceModule),
        typeof(IdentityWebApiModule),
        typeof(IdentityEfCoreModule),
        typeof(AuditLogsEfCoreModule),
        typeof(PermissionsEfCoreModule),
        typeof(SettingsEfCoreModule),
        typeof(TenantsEfCoreModule))]
    public class IdentityWebHostModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var configuration = context.Services.GetConfiguration();

            var identityConfig = GetConfigurations(configuration);

            Configure<AbpDbContextOptions>(options =>
            {
                options.UseSqlServer();
            });

            Configure<AbpAuditingOptions>(options =>
            {
                options.IsEnabledForGetRequests = true;

                options.ApplicationName = IdentityModuleConsts.ProjectName;
            });

            // authentication
            ConfigurationAuthentication(context, identityConfig);

            // swagger
            ConfigurationSwagger(context, identityConfig);

            // redis
            ConfigurationRedis(context, configuration);
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            var app = context.GetApplicationBuilder();
            var identityConfig = GetConfigurations(context.GetConfiguration());

            app.UseCorrelationId();
            app.UseVirtualFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAbpClaimsMap();

            if (BranchConsts.IsMultiTenancyEnabled)
            {
                app.UseMultiTenancy();
            }

            app.UseAbpRequestLocalization();

            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint($"/swagger/{identityConfig.Swagger.Name}/swagger.json", identityConfig.Swagger.Title);

                options.DocumentTitle = identityConfig.Swagger.Title;

                options.DocExpansion(DocExpansion.None);
            });

            app.UseAuditing();

            app.UseConfiguredEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }

        #region Private

        private IdentityConfigurations GetConfigurations(IConfiguration configuration)
        {
            var rootConfiguration = new IdentityConfigurations();

            var identityConfig = configuration.GetSection(IdentityConfigurations.IdentityPath);

            identityConfig.GetSection(IdentityConfigurations.AuthenticationPath).Bind(rootConfiguration.Authentication);

            identityConfig.GetSection(IdentityConfigurations.SwaggerPath).Bind(rootConfiguration.Swagger);

            return rootConfiguration;
        }

        private void ConfigurationRedis(
            ServiceConfigurationContext context,
            IConfiguration configuration)
        {
            context.Services.AddStackExchangeRedisCache(options =>
            {
                options.Configuration = configuration["Redis:Configuration"];
            });

            var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);

            context.Services.AddDataProtection()
                .PersistKeysToStackExchangeRedis(redis, "Branch-DataProtection-Keys");
        }

        private void ConfigurationSwagger(
            ServiceConfigurationContext context,
            IdentityConfigurations identityConfig)
        {
            context.Services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc(
                   name: identityConfig.Swagger.Name,
                   info: new OpenApiInfo
                   {
                       Title = identityConfig.Swagger.Title,
                       Version = identityConfig.Swagger.Version,
                       Description = identityConfig.Swagger.Description,
                       License = new OpenApiLicense
                       {
                           Name = identityConfig.Swagger.LicenseName,
                           Url = new Uri(identityConfig.Swagger.LicenseUrl)
                       },
                       TermsOfService = new Uri(identityConfig.Swagger.TermsOfService)
                   });

                options.DocInclusionPredicate((docName, description) => true);

                options.CustomSchemaIds(type => type.FullName);

                options.OperationFilter<AddResponseHeadersFilter>();

                options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();

                options.OperationFilter<SecurityRequirementsOperationFilter>();

                options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.OAuth2,
                    Flows = new OpenApiOAuthFlows
                    {
                        Implicit = new OpenApiOAuthFlow
                        {
                            AuthorizationUrl = new Uri($"{identityConfig.Authentication.Authority}/connect/authorize"),
                            Scopes = new Dictionary<string, string>
                            {
                                {
                                    "Identity","Identity api"
                                }
                            }
                        }
                    }
                });
            });
        }

        private void ConfigurationAuthentication(
            ServiceConfigurationContext context,
            IdentityConfigurations identityConfig)
        {
            context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = identityConfig.Authentication.Authority;
                    options.ApiName = identityConfig.Authentication.ApiName;
                    options.RequireHttpsMetadata = identityConfig.Authentication.RequireHttpsMetadata;
                });
        }

        #endregion
    }
}
